package com.cskaoyan.javase.annotation._3handle;

import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

/**
 * 注解处理器的案例:
 * 先定义一个Person类，类中有两个成员变量age和name。
 *      然后再定义2个注解，用于修饰成员变量name和age，其中：
 *      注解AgeLimit 用于限定成员变量age必须处在1-150之间。
 *      注解NameLimit 用于限定成员变量name的字符串长度不超过4。
 * 然后定义注解处理器，使得不满足条件的Person对象无法创建。
 *
 * @since 11:02
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) throws Exception {
        // Person p = new Person(500, "爱新觉罗玄烨");
        // System.out.println(p);

        // 用工厂创建对象,工厂会排除不合法的对象,不能创建不合法对象
        Person p = PersonInstanceFactory.getPersonInstance(100, "李白的三十代孙子");
        System.out.println(p);
    }
}


@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface AgeLimit {
    int max() default 150;

    int min() default 1;
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface NameLimit {
    int value() default 4;
}

// 工厂的设计模式,注解处理器
class PersonInstanceFactory {

    // 该方法用于生产合格的,符合要求的Person
    public static Person getPersonInstance(int age, String name) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 1.先获取注解
        // 获取Person类的Class对象
        Class<?> personClazz = Class.forName("com.cskaoyan.javase.annotation._3handle.Person");
        Field ageField = personClazz.getDeclaredField("age");
        Field nameField = personClazz.getDeclaredField("name");
        // 获取age的限制注解,并处理
        // 2.拿到注解描述的额外信息,对象age和name的限制条件
        if (ageField.isAnnotationPresent(AgeLimit.class)) {
            // 有限制age取值的注解,执行相关处理
            AgeLimit ageAnno = ageField.getAnnotation(AgeLimit.class);
            int maxAge = ageAnno.max();
            int minAge = ageAnno.min();
            // 3.判断age
            if (judgeAge(age, maxAge, minAge)) {
                // 规定true表示数据不合法,不在范围内,抛出异常
                throw new PersonArgsException("age属性不在取值范围,不合法!");
            }
        } else {
            System.out.println("age属性没有被限制!");
        }
        if (nameField.isAnnotationPresent(NameLimit.class)) {
            // 有限制name取值的注解,执行相关处理
            // 4.判断name
            // 有限制age取值的注解,执行相关处理
            NameLimit nameAnno = nameField.getAnnotation(NameLimit.class);
            int maxLength = nameAnno.value();
            // 3.判断name
            if (judgeName(name, maxLength)) {
                // 规定true表示数据不合法,不在范围内,抛出异常
                throw new PersonArgsException("name属性的长度不合法!");
            }

        } else {
            System.out.println("name属性没有被限制!");
        }
        // 5.如果都符合要求,用反射创建对象,返回该对象
        // 获取双参的私有构造器
        Constructor<?> constructor = personClazz.getDeclaredConstructor(int.class, String.class);
        constructor.setAccessible(true);
        return ((Person) constructor.newInstance(age, name));
    }

    // 该方法用于校验name的长度,true表示不合法
    private static boolean judgeName(String name, int maxLength) {
        return name.length() > maxLength;
    }

    // 该方法用于校验age的取值是否在合法范围内,true表示不合法
    private static boolean judgeAge(int age, int maxAge, int minAge) {
        return age > maxAge || age < minAge;
    }
}

class PersonArgsException extends RuntimeException {
    public PersonArgsException(String message) {
        super(message);
    }
}

class Person {
    @AgeLimit
    private int age;
    @NameLimit
    private String name;

    // 既然希望注解带来的限制信息能够生效,那么该类型创建对象,必须要依赖于注解处理器,构造器必须私有,然后在外部的注解处理器中用反射创建对象
    private Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("Person{");
        sb.append("age=").append(age);
        sb.append(", name='").append(name).append('\'');
        sb.append('}');
        return sb.toString();
    }
}